package com.kede.pay.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.kede.api.ienum.VipEnum;
import com.kede.pay.config.WxPayConfig;
import com.kede.pay.domain.*;
import com.kede.pay.enumstatus.AppIdTypeEnum;
import com.kede.pay.enumstatus.TransferStatusEnum;
import com.kede.pay.pojo.po.PayPO;
import com.kede.pay.pojo.po.VipPayPO;
import com.kede.pay.service.*;
import com.kede.pojo.ResHttp;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.service.payments.app.AppServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
import com.wechat.pay.java.service.transferbatch.TransferBatchService;
import com.wechat.pay.java.service.transferbatch.model.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

/***
 ** @Author:Pr.li
 ** @Description: 微信支付相关服务
 ** @Date: create in 2023/5/4 14:44
 ***/
@Service
@Slf4j
public class WxPayServiceImpl  implements WxPayService {

    @Resource
    private RSAAutoCertificateConfig config;

    @Resource
    private WxPayConfig wxPayConfig;

    public static TransferBatchService transferBatchService;

    @Resource
    private UsersService usersService;

    @Resource
    private TransferBatchLogService batchLogService;
    @Resource
    private TransferLogService logService;

    @Resource
    private ConfigService configService;

    @Resource
    private UserMoneyLogService userMoneyLogService;

    /**
     * 批量转账
     */
    private final static String _batchTranNoKey = "TNN";
    /**
     * 单个转账
     */
    private final static String _tranNoKey = "TSN";

    public static JsapiServiceExtension jsapiServiceExtension;
    public static AppServiceExtension appServiceExtension;

    @PostConstruct
    public void init(){
        transferBatchService = new TransferBatchService.Builder().config(config).build();
        jsapiServiceExtension = new JsapiServiceExtension.Builder().config(config).build();
        appServiceExtension = new AppServiceExtension.Builder().config(config).build();
    }

    /** 通过微信批次单号查询批次单 */
    @Override
    public TransferBatchEntity getTransferBatchByNo(GetTransferBatchByNoRequest request) {
        return transferBatchService.getTransferBatchByNo(request);
    }

    /** 通过商家批次单号查询批次单 */
    @Override
    public TransferBatchEntity getTransferBatchByOutNo(GetTransferBatchByOutNoRequest request) {
        return transferBatchService.getTransferBatchByOutNo(request);
    }

    /** 发起商家转账 */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public InitiateBatchTransferResponse initiateBatchTransfer(List<TransferLog> detailInputList) {
        InitiateBatchTransferRequest request = new InitiateBatchTransferRequest();
        AppIdTypeEnum appIdTypeEnum = AppIdTypeEnum.valueOfCode(detailInputList.get(0).getType());
        if (appIdTypeEnum==null){
            throw new RuntimeException("type 无效");
        }
        request.setAppid(appIdTypeEnum.getAppid());
        String batchNo = getRandomNo(_batchTranNoKey);
        request.setOutBatchNo(batchNo);
        request.setBatchName("用户提现");
        int i = 0;
        BigDecimal totalAmout= new BigDecimal("0");
        List<TransferDetailInput> transferDetailInputList = new ArrayList<>();
        for (TransferLog input : detailInputList) {
            LambdaQueryWrapper<UsersDomain> usersDomainLambdaQueryWrapper = new LambdaQueryWrapper<>();
            usersDomainLambdaQueryWrapper.eq(UsersDomain::getId,input.getUserId());
            //查询user 信息
            UsersDomain user = usersService.getOne(usersDomainLambdaQueryWrapper);
            if (user==null) {
                continue;
            }
            input.setOutDetailNo(getRandomNo(_tranNoKey));
            input.setOutBatchNo(batchNo);
            input.setDetailStatus(TransferStatusEnum.PROCESSING.toString());
            TransferDetailInput transferDetailInput = input.toTransferDetailInput();
            // 记录提现
            LambdaQueryWrapper<UserMoneyLogDomain> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(UserMoneyLogDomain::getUserId,input.getUserId());
            queryWrapper.orderByDesc(UserMoneyLogDomain::getId);
            queryWrapper.last("limit 1");
            UserMoneyLogDomain beforLog = userMoneyLogService.getOne(queryWrapper);
            UserMoneyLogDomain userMoneyLogDomain = new UserMoneyLogDomain();
            userMoneyLogDomain.setUserId(input.getUserId());
            BigDecimal beformoney = beforLog.getAfter();
            BigDecimal money = new BigDecimal("-" + input.getTransferAmount().toString());

            userMoneyLogDomain.setBefore(beformoney);
            //计算手续费
            BigDecimal rateMoney = getWithdrawalRate().multiply(input.getTransferAmount()).setScale(2, RoundingMode.HALF_UP);;
            BigDecimal subtract = beformoney.add(money).subtract(rateMoney);
            userMoneyLogDomain.setAfter(subtract);
            userMoneyLogDomain.setMoney(money);
            userMoneyLogDomain.setRateMoney(rateMoney);
            BigDecimal allMoney = null;
            if (subtract.compareTo(new BigDecimal("0")) == -1){
                //提现后余额为负数了
                //先扣手续费  然后剩余的钱全部提出
                allMoney = beformoney.subtract(rateMoney);
                userMoneyLogDomain.setMoney(allMoney.negate());
                transferDetailInput.setTransferAmount(allMoney.multiply(new BigDecimal("100")).longValue());
                userMoneyLogDomain.setAfter(new BigDecimal("0.00"));
                input.setTransferAmount(allMoney);
            }
            logService.save(input);
            //以下计算批量数据
            i++;
            if (allMoney!=null){
                totalAmout = totalAmout.add(allMoney);
            }else {
                totalAmout = totalAmout.add(input.getTransferAmount());
            }
            //以上计算批量数据 end
            transferDetailInputList.add(transferDetailInput);
            //与php 逻辑兼容
            userMoneyLogDomain.setChangeType(1);
            userMoneyLogDomain.setOrderSn(input.getOutDetailNo());
            userMoneyLogDomain.setMemo("微信转账到零钱");
            user.setUserMoney(userMoneyLogDomain.getAfter());
            usersService.updateById(user);
            userMoneyLogService.save(userMoneyLogDomain);
        }
        //详细转账单信息
        request.setTransferDetailList(transferDetailInputList);
        request.setTotalAmount(totalAmout.multiply(new BigDecimal("100")).longValue());

//        转账总笔数
        request.setTotalNum(i);
        request.setBatchRemark(detailInputList.get(0).getTransferRemark());
        log.info("收到转账申请");
        log.info("detailInputList=={}",detailInputList);
        log.info("batchTransferRequest=={}",request);

        TransferBatchLog transferBatchLog = new TransferBatchLog();
        transferBatchLog.setBatchName(request.getBatchName());
        transferBatchLog.setBatchRemark(request.getBatchRemark());
        transferBatchLog.setOutBatchNo(request.getOutBatchNo());
        transferBatchLog.setTotalAmount(totalAmout);
        transferBatchLog.setTotalNum(request.getTotalNum());
        batchLogService.save(transferBatchLog);
        return transferBatchService.initiateBatchTransfer(request);
    }
    /** 通过微信明细单号查询明细单 */
    @Override
    public TransferDetailEntity getTransferDetailByNo(GetTransferDetailByNoRequest request) {
        return transferBatchService.getTransferDetailByNo(request);
    }
    /** 通过userId查询明细单 */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void getTransferDetailByUserid(Integer userId) {
        LambdaQueryWrapper<UsersDomain> usersDomainLambdaQueryWrapper = new LambdaQueryWrapper<>();
        usersDomainLambdaQueryWrapper.eq(UsersDomain::getId,userId);
        //查询user 信息
        UsersDomain user = usersService.getOne(usersDomainLambdaQueryWrapper);
        if (user==null) {
            return;
        }
//        LambdaQueryWrapper<TransferLog> queryWrapper = new LambdaQueryWrapper<>();
//        queryWrapper.eq(TransferLog::getUserId,userId);
//        queryWrapper.eq(TransferLog::getDetailStatus, TransferStatusEnum.WAIT_PAY);
//        List<TransferLog> waitList = logService.list(queryWrapper);
        LambdaQueryWrapper<TransferLog> queryPROCESSING = new LambdaQueryWrapper<>();
        queryPROCESSING.eq(TransferLog::getUserId,userId);
        queryPROCESSING.eq(TransferLog::getDetailStatus, TransferStatusEnum.PROCESSING);
        List<TransferLog> proList = logService.list(queryPROCESSING);
//        waitList.addAll(proList);
        //获取最新的余额变动记录
        LambdaQueryWrapper<UserMoneyLogDomain> newMonlogWrapper = new LambdaQueryWrapper<>();
        newMonlogWrapper.eq(UserMoneyLogDomain::getUserId,userId);
        newMonlogWrapper.orderByDesc(UserMoneyLogDomain::getId);
        newMonlogWrapper.last("limit 1");
        UserMoneyLogDomain beforLog = userMoneyLogService.getOne(newMonlogWrapper);
        for (TransferLog transferLog : proList) {
            GetTransferDetailByOutNoRequest getTransferDetailByOutNoRequest = new GetTransferDetailByOutNoRequest();
            getTransferDetailByOutNoRequest.setOutDetailNo(transferLog.getOutDetailNo());
            getTransferDetailByOutNoRequest.setOutBatchNo(transferLog.getOutBatchNo());
            TransferDetailEntity transferDetailByOutNo = transferBatchService.getTransferDetailByOutNo(getTransferDetailByOutNoRequest);
            log.info("收到微信详情单 =={}",transferDetailByOutNo);
            //其中有人转账失败
            if (transferDetailByOutNo.getDetailStatus().equals(TransferStatusEnum.FAIL.toString())) {
                //找出这笔转账时的手续费 也给退还到账户
                LambdaQueryWrapper<UserMoneyLogDomain> moneyLogQuery = new LambdaQueryWrapper<>();
                moneyLogQuery.eq(UserMoneyLogDomain::getOrderSn,transferLog.getOutDetailNo());
                UserMoneyLogDomain one = userMoneyLogService.getOne(moneyLogQuery);
                BigDecimal rateMoney = one.getRateMoney();
                //添加失败记录
                UserMoneyLogDomain userMoneyLogDomain = new UserMoneyLogDomain();
                userMoneyLogDomain.setUserId(userId);
                BigDecimal beformoney = beforLog.getAfter();
                //记录返回的金额
                BigDecimal moneyLong = new BigDecimal(transferDetailByOutNo.getTransferAmount().toString());
                BigDecimal money = moneyLong.divide(new BigDecimal("100"));
                userMoneyLogDomain.setMoney(money.add(rateMoney));
                userMoneyLogDomain.setBefore(beformoney);
                BigDecimal subtract = beformoney.add(money).add(rateMoney);
                userMoneyLogDomain.setAfter(subtract);
                //与php 逻辑兼容
                userMoneyLogDomain.setChangeType(1);
                userMoneyLogDomain.setOrderSn(transferLog.getOutDetailNo());
                userMoneyLogDomain.setMemo("转账失败退还到账户");
                //因为是循环 这里将最新的记录
                beforLog = userMoneyLogDomain;
                user.setUserMoney(userMoneyLogDomain.getAfter());
                usersService.updateById(user);
                userMoneyLogService.save(userMoneyLogDomain);
                //修改转账单状态
                transferLog.setFailReason(getFailReasonStr(transferDetailByOutNo.getFailReason()));
                transferLog.setDetailStatus(TransferStatusEnum.FAIL.toString());
                logService.updateById(transferLog);
                // 要么成功 要么失败 否则会一直查询这个单的状态
            }else if(transferDetailByOutNo.getDetailStatus().equals(TransferStatusEnum.SUCCESS.toString())){
                transferLog.setDetailStatus(TransferStatusEnum.SUCCESS.toString());
                logService.updateById(transferLog);
            }
        }
    }

    public static String getFailReasonStr(FailReasonType failReasonType){
        switch (failReasonType){
            case ACCOUNT_FROZEN:
                return " 该用户账户被冻结";

            case REAL_NAME_CHECK_FAIL:
                return "收款人未实名认证，需要用户完成微信实名认证";
            case NAME_NOT_CORRECT:
                return "收款人姓名校验不通过，请核实信息";
            case OPENID_INVALID:
                return "Openid格式错误或者不属于商家公众账号";
            case TRANSFER_QUOTA_EXCEED:
                return "超过用户单笔收款额度，核实产品设置是否准确";
            case DAY_RECEIVED_QUOTA_EXCEED:
                return "超过用户单日收款额度，核实产品设置是否准确";
            case MONTH_RECEIVED_QUOTA_EXCEED:
                return " 超过用户单月收款额度，核实产品设置是否准确";
            case DAY_RECEIVED_COUNT_EXCEED:
                return "超过用户单日收款次数，核实产品设置是否准确";
            case PRODUCT_AUTH_CHECK_FAIL:
                return "未开通该权限或权限被冻结，请核实产品权限状态";
            case OVERDUE_CLOSE:
                return "超过系统重试期，系统自动关闭";
            case ID_CARD_NOT_CORRECT:
                return "收款人身份证校验不通过，请核实信息";
            case ACCOUNT_NOT_EXIST:
                return "该用户账户不存在";
            case TRANSFER_RISK:
                return "该笔转账可能存在风险，已被微信拦截";
            case OTHER_FAIL_REASON_TYPE:
                return "其它失败原因";
            case REALNAME_ACCOUNT_RECEIVED_QUOTA_EXCEED:
                return "请引导用户在微信支付查看详情";
            case RECEIVE_ACCOUNT_NOT_PERMMIT:
                return "请在产品设置中调整，添加该用户为收款人";
            case PAYEE_ACCOUNT_ABNORMAL:
                return "请联系用户完善其在微信支付的身份信息以继续收款";
            case PAYER_ACCOUNT_ABNORMAL:
                return "可前往商户平台获取解除功能限制指引";
            case TRANSFER_SCENE_UNAVAILABLE:
                return " 该转账场景暂不可用，请确认转账场景ID是否正确";
            case TRANSFER_SCENE_INVALID:
                return "你尚未获取该转账场景，请确认转账场景ID是否正确";
            case TRANSFER_REMARK_SET_FAIL:
                return "转账备注设置失败， 请调整后重新再试";
            case RECEIVE_ACCOUNT_NOT_CONFIGURE:
                return "请前往商户平台-商家转账到零钱-前往功能-转账场景中添加";
            case BLOCK_B2C_USERLIMITAMOUNT_BSRULE_MONTH:
                return "超出用户单月转账收款20w限额，本月不支持继续向该用户付款";
            case BLOCK_B2C_USERLIMITAMOUNT_MONTH:
                return "用户账户存在风险收款受限，本月不支持继续向该用户付款";
            case MERCHANT_REJECT:
                return "商户员工（转账验密人）已驳回转账";
            case MERCHANT_NOT_CONFIRM:
                return "商户员工（转账验密人）超时未验密";
            default:
                return "未知原因";
        }

    }

    /**
     * 返回32位有顺序的随机字符串
     * @return
     */
    public String getRandomNo(String key){
        StringBuilder stringBuilder = new StringBuilder();
        String yyyyMMddHHmm = DateUtil.format(LocalDateTime.now(), "yyyyMMddHHmm");
        String snowflakeNextIdStr = IdUtil.getSnowflakeNextIdStr();
        stringBuilder.append(key).append(yyyyMMddHHmm.substring(2)).append(snowflakeNextIdStr);
        return stringBuilder.toString();
    }

    /**
     * 获得提现的费率 默认 0.006
     * @return
     */
    @Override
    public BigDecimal getWithdrawalRate(){
        try {
            LambdaQueryWrapper<ConfigDomain> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(ConfigDomain::getName,"WithdrawalRate");
            ConfigDomain one = configService.getOne(queryWrapper);
            return new BigDecimal(one.getValue());
        }catch (Exception e){
            return new BigDecimal("0.006");
        }
    }

    @Value("${wxpay-notify-domain}")
    private String wxpayNotifyDomain;

    /**
     * 微信支付 下单并返回调起微信支付
     * @return
     */
    @Override
    public ResHttp vipPayment(VipPayPO vipPayPO) {

        String snKey = VipEnum.Vip1.getCode();
        if(vipPayPO.getVip().equals(VipEnum.Vip2.getValue())){
            snKey = VipEnum.Vip2.getCode();
        }

        int money = vipPayPO.getMoney().multiply(new BigDecimal("100")).intValue();
        AppIdTypeEnum appIdTypeEnum = AppIdTypeEnum.valueOfCode(vipPayPO.getType());
        log.debug("vipPayment=={}",vipPayPO);
        switch (appIdTypeEnum){
            case USER_XCX:
                PrepayWithRequestPaymentResponse prepayWithRequestPaymentResponse = payUSER_XCX(vipPayPO.getOpenid(), snKey, vipPayPO.getDescription(),
                        money, vipPayPO.getAttach());
                log.debug("小程序payment下单返沪的结果是",prepayWithRequestPaymentResponse.toString());
                return ResHttp.okData(prepayWithRequestPaymentResponse);

            case USERAPP:
                com.wechat.pay.java.service.payments.app.model.PrepayWithRequestPaymentResponse response =
                        payUSERAPP(snKey, money, vipPayPO.getDescription(), vipPayPO.getAttach());
                log.debug("app  Vippayment下单返沪的结果是=={}",response.toString());
                return ResHttp.okData(response);
        }

        return ResHttp.ok();

    }

    @Override
    public ResHttp payment(PayPO payPO) {
        int money = payPO.getMoney().multiply(new BigDecimal("100")).intValue();
        AppIdTypeEnum appIdTypeEnum = AppIdTypeEnum.valueOfCode(payPO.getType());
        switch (appIdTypeEnum){
            case USER_XCX:
                PrepayWithRequestPaymentResponse prepayWithRequestPaymentResponse = payUSER_XCX(payPO.getOpenid(), payPO.getSnKey(), payPO.getDescription(),
                        money, payPO.getAttach());
                log.debug("小程序payment下单返沪的结果是",prepayWithRequestPaymentResponse.toString());
                return ResHttp.okData(prepayWithRequestPaymentResponse);

            case USERAPP:
                com.wechat.pay.java.service.payments.app.model.PrepayWithRequestPaymentResponse response =
                        payUSERAPP(payPO.getSnKey(), money, payPO.getDescription(), payPO.getAttach());
                log.debug("app  Vippayment下单返沪的结果是=={}",response.toString());
                return ResHttp.okData(response);
        }

        return ResHttp.ok();
    }

    public com.wechat.pay.java.service.payments.app.model.PrepayWithRequestPaymentResponse payUSERAPP(
            String snKey, Integer money,String description,String attach
    ){
        log.debug("payUSERAPP 执行了");
        com.wechat.pay.java.service.payments.app.model.PrepayRequest request = new
                com.wechat.pay.java.service.payments.app.model.PrepayRequest();
        request.setAppid(wxPayConfig.getAppAppid());
        com.wechat.pay.java.service.payments.app.model.Amount amount = new com.wechat.pay.java.service.payments.app.model.Amount();
        amount.setTotal(money);
        request.setAmount(amount);
        request.setMchid(wxPayConfig.getMchId());
        request.setDescription(description);
        request.setOutTradeNo(getRandomNo(snKey));
        request.setAttach(attach);
        request.setNotifyUrl(wxpayNotifyDomain);
        com.wechat.pay.java.service.payments.app.model.PrepayWithRequestPaymentResponse prepayWithRequestPaymentResponse =
                appServiceExtension.prepayWithRequestPayment(request);
        log.debug("app Pay返回的参数为=={}", prepayWithRequestPaymentResponse);
        return prepayWithRequestPaymentResponse;
    }

    /**
     * 小程序支付
     * @param openid
     * @param snKey
     * @param description
     * @param money
     * @return
     */
    public PrepayWithRequestPaymentResponse payUSER_XCX(String openid, String snKey,String description,Integer money,String attach){
        PrepayRequest request = new PrepayRequest();
        request.setAppid(AppIdTypeEnum.USER_XCX.getAppid());
        request.setMchid(wxPayConfig.getMchId());
        request.setDescription(description);
        //商户自己随机生成的订单号
        request.setOutTradeNo(getRandomNo(snKey));
        request.setNotifyUrl(wxpayNotifyDomain);
//        request.setTimeExpire(getPayMentTimeExpire());
        Amount amount = new Amount();
        amount.setTotal(money);
        request.setAmount(amount);
        Payer payer = new Payer();
        payer.setOpenid(openid);
        request.setPayer(payer);
        request.setAttach(attach);
        PrepayWithRequestPaymentResponse prepayWithRequestPaymentResponse = jsapiServiceExtension.prepayWithRequestPayment(request);
        return prepayWithRequestPaymentResponse;
    }

    /**
     * 获取支付订单的过期时间
     * 有效期10分钟
     */
    public static String getPayMentTimeExpire(){
        OffsetDateTime offsetDateTime = LocalDateTime.now().atOffset(ZoneOffset.ofHours(8)).plusMinutes(10L);
        return offsetDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX"));
    }


}
